/*
	Simple Software Renderer (SSRenderer)
	Copyright (C) 2007 Piotr Orzechowski <piotr.orzechowski@gmail.com>

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include <cmath>
#include <cassert>
#include "./Profiler.h"

namespace util
{

Profiler* Profiler::m_pInstance = NULL;

void Profiler::StartProfileLoop()
{
	m_LoopStartTime = m_Timer.GetElapsedSeconds();
	m_pTempRoot = &m_Samples["-root-"];
	++m_pTempRoot->timesCalled;
	m_pTempPrev = NULL;

	// Clear duration of all samples
	for(profmap::iterator itr = m_Samples.begin(); itr != m_Samples.end(); ++itr)
	{
		itr->second.durationWithChildren	= 0.0f;
		itr->second.durationWithoutChildren = 0.0f;
	}
}

void Profiler::StopProfileLoop()
{
	// Should always be true
	assert(m_ProfCount == 0 && "PROFILER : Number of PROFILE_BEGIN statements doesn't match number of PROFILE_END statements!");

	float loopDuration = m_Timer.GetElapsedSeconds() - m_LoopStartTime;

	if (loopDuration < 0.00001f)
	{
		// The loop was too short
		return;
	}

	// Manually update the root node (now m_pTempRoot points at '-root-' sample);
	m_pTempRoot->durationWithChildren = loopDuration;
	m_pTempRoot->durationWithoutChildren += loopDuration;
	// @wniosek - trzeba bylo potraktowac root jako zwykly korzen

	// Update all samples
	ProfileSample *sample;
	float percentage;

	for(profmap::iterator itr = m_Samples.begin(); itr != m_Samples.end(); ++itr)
	{
		sample = &itr->second;

		// Compute average time consumption of the sample
		percentage = sample->durationWithChildren / loopDuration * 100.0f;

		sample->avrAccumulator += percentage;
		sample->avrDurPercent = sample->avrAccumulator / sample->timesCalled;

		// Find min and max times of the sample
		if (percentage < sample->minDurPercent)
		{
			sample->minDurPercent = percentage;
		}
		if (percentage > sample->maxDurPercent)
		{
			sample->maxDurPercent = percentage;
		}
	}
}

void Profiler::BeginSample(std::string name)
{
	assert(name != "-root-" && "PROFILER : You can't use '-root-' as a name of the profile!");

	ProfileSample &sample = m_Samples[name];

	if (sample.name[0] == '\0')
	{
		// Make a name ID for this sample (ONLY once)
		for(unsigned int i = 0; i < sample.NAME_LENGTH - 1; ++i)
		{
			if (i < name.length())
			{
				sample.name[i] = name.c_str()[i];
			}
			else
			{
				sample.name[i] = ' ';
			}
		}
		sample.name[sample.NAME_LENGTH - 1] = '\0';
	}

	sample.timesCalled++;

	// Build N-tree-doublylinked-whatsoever-list
	if (m_pTempPrev != NULL)
	{
		m_pTempPrev->pNext = &sample;
	}

	if (m_pTempRoot->pChild == NULL)
	{
		m_pTempRoot->pChild = &sample;
	}

	sample.pPrev = m_pTempPrev;
	sample.pNext = sample.pChild = NULL;
	sample.pRoot = m_pTempRoot;
	m_pTempRoot = &sample;

	sample.startTime = m_Timer.GetElapsedSeconds();

	// Increase the number of currently profiled samples
	++m_ProfCount;
}

void Profiler::EndSample()
{
	assert(m_ProfCount > 0 && "PROFILER : No matching PROFILE_BEGIN for the PROFILE_END!");

	// Decrease the number of currently profiled samples
	--m_ProfCount;

	// Find duration of the sample
	float endTime = m_Timer.GetElapsedSeconds();

	m_pTempRoot->pNext = m_pTempRoot->pRoot->pChild;

	m_pTempRoot->durationWithChildren = endTime - m_pTempRoot->startTime;
	m_pTempRoot->durationWithoutChildren += m_pTempRoot->durationWithChildren;

	// Evert child node decreases durationWithoutChildren of its parent node
	m_pTempRoot->pRoot->durationWithoutChildren -= m_pTempRoot->durationWithChildren;

	m_pTempPrev = m_pTempRoot;
	m_pTempRoot->pRoot->pChild->pPrev = m_pTempRoot;
	m_pTempRoot = m_pTempRoot->pRoot;
}

void Profiler::PrevSample()
{
	m_pSelected = m_pSelected->pPrev;
}

void Profiler::NextSample()
{
	m_pSelected = m_pSelected->pNext;
}

void Profiler::RootSample()
{
	if (m_pSelected->pRoot != NULL)
	{
		m_pSelected = m_pSelected->pRoot;
	}
}

void Profiler::ChildSample()
{
	if (m_pSelected->pChild != NULL)
	{
		m_pSelected = m_pSelected->pChild;
	}
}

void Profiler::SelectSample(const char* name)
{
	if (m_Samples.count(name) != 0)
	{
		m_pSelected = &m_Samples[name];
	}
}

const ProfileSample* Profiler::GetSelectedSample()
{
	return m_pSelected;
}

const ProfileSample* Profiler::GetSample(const char* name)
{
	if (m_Samples.count(name) != 0)
	{
		return &m_Samples[name];
	}
	else
	{
		return NULL;
	}
}

void Profiler::GetOutput(const char* &buffer)
{
	static char buff[4096];		// @Important: Fixed buffer size
	buffer = buff;

	char* ptr = buff;

    ::sprintf(buff, 	"      Sample name    Min [%%]   Max [%%]   Avr [%%]  Self [ms]   Times called\n");

	ptr = buff + ::strlen(buff);

	ProfileSample *first = m_pSelected;
	ProfileSample *current = first;

	if (current == NULL)
	{
		return;
	}

	char selection;
	char children;
	do
	{
		if (current == m_pSelected) selection = '>';
		else selection = ' ';

		if (current->pChild != NULL) children = '+';
		else children = ' ';


		::sprintf(ptr, "%c [%c] %s    %6.2f    %6.2f    %6.2f    %6.2f %12d\n",
						selection,
						children,
						current->name,
						current->minDurPercent,
						current->maxDurPercent,
						current->avrDurPercent,
						current->durationWithoutChildren * 1000.0f,
						current->timesCalled);
		ptr = buff + ::strlen(buff);

		current = current->pNext;
	}
	while(current != first);
}

} // namespace

